import gevent
from gevent import wsgi, monkey, event
monkey.patch_all()
from datetime import datetime, timedelta
import urlparse
import hashlib
from collections import defaultdict
import time

crossdomainxml = """
<?xml version="1.0" encoding="UTF-8"?> 
<cross-domain-policy xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.adobe.com/xml/schemas/PolicyFile.xsd"> 
<allow-access-from domain="*" /> 
</cross-domain-policy>"""

RC4KEY = "The fortune of apples is not to be oranges, yet remain green, and strangely, ripe!"

def RC4(data, key):
    x = 0
    s = range(256)
    for i in range(256):
        x = (x + s[i] + ord(key[i % len(key)])) % 256
        s[i], s[x] = s[x], s[i]
    x = y = 0
    out = ""
    for c in data:
        x = (x + 1) % 256
        y = (y + s[x]) % 256
        s[x], s[y] = s[y], s[x]
        out += chr(ord(c) ^ s[(s[x] + s[y]) % 256])
    return out


def digest(data):
    return hashlib.md5(data).hexdigest()


def http_message(code):
    return {
        200: "OK",
        404: "Not Found.",
        500: "DOH!",
        501: "I CAN'T DO THAT DAVE.",
        403: "ENTRY IS DENIED.",
        401: "YOU DON'T HAVE THE PASSWORD.",
    }.get(code, "HUH?")


class Status(Exception):
    def __init__(self, status, e=None):
        assert type(status) is int 
        self.status = status
        self.headers = {"Content-Type":"text/plain"}
        self.body = str(e or self.message)

    @property
    def message(self):
        return http_message(self.status)


class Request(object):
    def __init__(self, environ):
        self.environ = environ
        self.method = environ["REQUEST_METHOD"].lower()
        self.path = environ["PATH_INFO"]
        self.atoms = self.path.split("/")
        self.query = dict(urlparse.parse_qsl(environ["QUERY_STRING"]))
        self.body = environ["wsgi.input"].read()
        self.etag = environ.get("HTTP_IF_NONE_MATCH", None)
        self.row_version = environ.get("HTTP_ROW_VERSION", None)
        self.sig = environ.get("HTTP_SIG", None)
    

class Response(object):
    def __init__(self, environ):
        self.status = 200
        self.environ = environ
        self.headers = {"Content-Type":"text/plain"}
        self.body = ""
    
    @property
    def message(self):
        return http_message(self.status)


class Node(object):
    def __init__(self, data):
        self.data = data
        self.timestamp = datetime.now()
        self.row_version = 0

    @property
    def etag(self):
        return hashlib.md5(str(self.timestamp)+str(self.row_version)).hexdigest()

    def update(self, data):
        self.data = data
        self.row_version = self.row_version + 1
        self.timestamp = datetime.now()
        

class VolatileStorage(object):
    
    def __init__(self):
        self.store = {}
    
    def query(self, q):
        return (i for i in self.store.keys() if i.startswith(q))

    def get_data(self, q):
        return self.store.get(q, None)

    def set_data(self, q, data, row_version):
        if q in self.store:
            self.store[q].update(data)
        else:
            self.store[q] = Node(data)
        return self.store[q]


class DiskStorage(VolatileStorage):
    pass


class PostOffice(object): 
    def __init__(self):
        self.volatile_storage = VolatileStorage()
        self.disk_storage = DiskStorage()
        
    def application(self, environ, start_response):
        request = Request(environ)
        response = Response(environ)
        if request.path == "/crossdomain.xml":
            response.body = crossdomainxml
        else:
            try:
                self.dispatch(request, response)
            except Status as e:
                response = e 
            except Exception as e:
                response = Status(500, e)
        start_response("%s %s"%(response.status, response.message), response.headers.items())
        return [response.body]

    def dispatch(self, request, response):
        storage = self.disk_storage
        if request.atoms[1] == "volatile":
            storage = self.volatile_storage
        if request.method == "get":
            if request.path.endswith("/"):
                response.body = "\r\n".join(storage.query(request.path))
                if response.body == "": raise Status(404)
                response.body = RC4(response.body, RC4KEY)
            else:
                node = storage.get_data(request.path)
                if node is None: raise Status(404)
                rv = request.query.get("rv",None)
                T = time.time()
                while rv == str(node.row_version):
                    gevent.sleep(0)
                    if time.time() - T > 3600: break
                if request.etag == node.etag: raise Status(304)
                response.body = node.data
                response.headers["Etag"] = node.etag
                response.headers["Row-Version"] = str(node.row_version)
        elif request.method == "post":
            if request.sig != digest(request.body + RC4KEY):
                raise Status(401)
            node = storage.get_data(request.path)
            if node is not None:
                if request.row_version != str(node.row_version):
                    response.body = node.data
                    response.headers["Etag"] = node.etag
                    response.headers["Row-Version"] = str(node.row_version)
                    response.status = 409
                    return
            node = storage.set_data(request.path, request.body, request.row_version)
            log = "/volatile/log" + request.path
            self.volatile_storage.set_data(log, request.row_version, request.row_version)
            response.headers["Etag"] = node.etag
            response.headers["Row-Version"] = str(node.row_version)


def main():
    app = PostOffice()
    print "Starting PostBox."
    wsgi.WSGIServer(('', 8888), app.application, backlog=1024).serve_forever()


if __name__ == "__main__":
    main()



